package com.example.authorization.conf;

import com.example.authorization.authentication.OAuth2ConfigurerUtils;
import com.example.authorization.authentication.PasswordCredentialsAuthenticationConverter;
import com.example.authorization.authentication.PasswordCredentialsAuthenticationProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(jsr250Enabled = true, securedEnabled = true)
public class AuthorizationServerSecurityConfig {
    /**
     * 配置认证断点的过滤器
     *
     * @param http 核心配置类
     * @return 认证过滤器链
     * @throws Exception 异常信息
     */
    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http
            , UserDetailsService userDetailsService
            , PasswordEncoder passwordEncoder)
            throws Exception {
        //默认的设置，点击源码其实就是配置了认证端点的csrf校验
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                .tokenEndpoint(endpointConfigurer -> endpointConfigurer.authenticationProviders(authenticationProviders
                        -> authenticationProviders.add(new PasswordCredentialsAuthenticationProvider(
                        OAuth2ConfigurerUtils.getAuthorizationService(http)
                        , OAuth2ConfigurerUtils.getTokenGenerator(http)
                        , userDetailsService
                        , passwordEncoder
                ))).accessTokenRequestConverters(authenticationConverters -> authenticationConverters.add(new PasswordCredentialsAuthenticationConverter())))
                //授权端点
                .authorizationEndpoint(endpoint -> {
                    //配置自定义的授权页面
                    endpoint.consentPage("/oauth2/consent");
                })
                // 开启 OpenID Connect 1.0
                .oidc(Customizer.withDefaults());    // Enable OpenID Connect 1.0
        http
                // Redirect to the login page when not authenticated from the
                // authorization endpoint
                // 未登录时访问认证端点将被重定向至login页面
                .exceptionHandling((exceptions) -> exceptions
                        .defaultAuthenticationEntryPointFor(
                                new LoginUrlAuthenticationEntryPoint("/login"),
                                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                        )
                )
                // Accept access tokens for User Info and/or Client Registration
                // 处理使用access token访问用户信息端点和客户端注册端点
                .oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()));

        return http.build();
    }

    /**
     * 客户端持久化到数据库
     *
     * @param jdbcTemplate
     * @return
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
        return new JdbcRegisteredClientRepository(jdbcTemplate);
    }

    /**
     * OAuth2AuthorizationService 是一个中心组件，新的授权被存储，现有的授权被查询。
     * 当遵循特定的协议流程时，它被其他组件使用例如，客户端认证、授权许可处理、令牌内省、令牌撤销、动态客户端注册等。
     *
     * @param jdbcTemplate
     * @return
     */
    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate) {
        return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository(jdbcTemplate));
    }

    /**
     * OAuth2AuthorizationConsentService 是一个中心组件，新的授权同意被存储，现有的授权同意被查询。
     * 它主要被实现OAuth2授权请求流程的组件使用，例如：authorization_code grant
     *
     * @param jdbcTemplate
     * @return
     */
    @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate) {
        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository(jdbcTemplate));
    }

    /**
     * 添加认证服务器配置
     *
     * @return AuthorizationServerSettings
     */
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().build();
    }

}
